/***************************************************************************
 *   Copyright (C) 2009 - 2010 by Simon Qian <SimonQian@SimonQian.com>     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "vsf.h"

enum ipheth_carrier_t
{
	IPHETH_CARRIER_ON = 0x04,
};

struct vsfusbh_ipheth_t
{
	struct vsfsm_pt_t initpt;
	struct vsfsm_t initsm;
	struct vsfsm_sem_t carrier_work_sem;
	struct vsftimer_t *timer;

	struct vsfhcd_urb_t *ctrlurb;
	struct vsfhcd_urb_t *inurb;
	struct vsfsm_t insm;
	struct vsfhcd_urb_t *outurb;
	struct vsfsm_t outsm;

	struct vsfusbh_device_t *dev;
	struct vsfusbh_t *usbh;
	uint8_t ifnum;
	uint8_t alt;
	bool connected;

	uint8_t ctrlbuff[6];		// for mac address and events
	struct vsfip_netdrv_t *netdrv;
	enum ipheth_carrier_t carrier;
};
struct vsfip_netdrv_cb_t vsfusbh_ipheth_cb;

#define IPHETH_MTU						1516
#define IPHETH_REQ_GETMAC				0x00
#define IPHETH_REQ_UNKNOWN02			0x02
#define IPHETH_REQ_UNKNOWN03			0x03
#define IPHETH_REQ_CARRIER_CHECK		0x45

static struct vsfsm_state_t *
vsfusbh_ipheth_urb_evthandler(struct vsfsm_t *sm, vsfsm_evt_t evt)
{
	struct vsfusbh_ipheth_t *ipheth = (struct vsfusbh_ipheth_t *)sm->user_data;
	struct vsfip_netdrv_t *netdrv = ipheth->netdrv;
	struct vsfhcd_urb_t *urb;
	bool isin;

	switch (evt)
	{
	case VSFSM_EVT_URB_COMPLETE:
		isin = sm == &ipheth->insm;
		urb = isin ? ipheth->inurb : ipheth->outurb;
		if (isin)
		{
			if ((urb->status != URB_OK) && (urb->transfer_buffer == ipheth->ctrlbuff))
			{
				vsfdbg_printf("ipheth: fail to get event" VSFCFG_DEBUG_LINEEND);
				if (ipheth->carrier == IPHETH_CARRIER_ON)
					vsfusbh_submit_urb(ipheth->usbh, urb);
				return NULL;
			}
			if ((urb->status == URB_OK) && (urb->actual_length > 2))
			{
				if (urb->actual_length == 4)
				{
					uint8_t *buffer = (uint8_t *)urb->transfer_buffer;
					bool connected = vsfip_netdrv_connected(netdrv);

					vsfdbg_prints("ipheth_evt: ");
					vsfdbg_printb(urb->transfer_buffer, urb->actual_length, VSFDBG_DF_U8_16_N);

					if (!connected && buffer[2])
					{
						// disable carrier_work timer, event will be checked by inurb
						vsftimer_dequeue(ipheth->timer);
						vsfdbg_prints("ipheth: Connected" VSFCFG_DEBUG_LINEEND);
						vsfip_netdrv_connect(netdrv);
					}
					else if (connected && !buffer[2])
					{
						// can not call vsfip_netdrv_on_inputted with VSFERR_FAIL
						//	because vsfip_netdrv_on_inputted will issue another input
						urb->free_buffer(urb->free_buffer_param);
						netdrv->rxbuf.netbuf = NULL;
						// netdrv will be connected in carrier_work
					}
					else
					{
						vsfip_netdrv_on_inputted(netdrv, VSFERR_FAIL);
					}
					vsfsm_sem_post(&ipheth->carrier_work_sem);
				}
				else
				{
					struct vsfip_netdrv_netbuf_t *rxbuf = &netdrv->rxbuf;

					rxbuf->buffer.buffer =
						vsfip_netdrv_header(netdrv, rxbuf->netbuf, -2);
					rxbuf->buffer.size = urb->actual_length - 2;
					vsfip_netdrv_on_inputted(netdrv, urb->status);
				}
			}
		}
		else
		{
			vsfip_netdrv_on_outputted(netdrv, urb->status);
		}
		break;
	default:
		break;
	}
	return NULL;
}

// netdrv
static vsf_err_t vsfusbh_ipheth_netdrv_init(struct vsfip_netdrv_t *netdrv)
{
	netdrv->mtu = IPHETH_MTU - VSFIP_ETH_HEADSIZE - 2;
	netdrv->header_size = VSFIP_ETH_HEADSIZE;
	netdrv->hwtype = VSFIP_ETH_HWTYPE;
	return VSFERR_NONE;
}

static vsf_err_t vsfusbh_ipheth_netdrv_fini(struct vsfip_netdrv_t *netdrv)
{
	return VSFERR_NONE;
}

static vsf_err_t vsfusbh_ipheth_netdrv_output(struct vsfip_netdrv_t *netdrv)
{
	struct vsfusbh_ipheth_t *ipheth = (struct vsfusbh_ipheth_t *)netdrv->param;
	struct vsfip_netdrv_netbuf_t *txbuf = &netdrv->txbuf;
	struct vsfhcd_urb_t *urb = ipheth->outurb;
	uint8_t *ptr = netdrv->txbuf.buffer.buffer;

	// rx_buffer is 32-bit aligned
	// rx_buffer + 2 is 16-bit aligned
	// after remove the first 16-bit header, eth packet will be 32-bit aligned
	// NOTE: hcd MUST accept 16-bit aligned transfer buffer
	ptr = vsfip_netdrv_header(netdrv, txbuf->netbuf, 2);
	if (!ptr) return VSFERR_FAIL;
	txbuf->buffer.buffer = ptr;
	txbuf->buffer.size += 2;

	urb->transfer_buffer = txbuf->buffer.buffer;
	urb->transfer_length = txbuf->buffer.size;
	urb->free_buffer = netdrv->adapter->free_buf;
	urb->free_buffer_param = netdrv->txbuf.netbuf;
	*(uint16_t *)urb->transfer_buffer = 0;
	return vsfusbh_submit_urb(ipheth->usbh, urb);
}

static vsf_err_t vsfusbh_ipheth_netdrv_input(struct vsfip_netdrv_t *netdrv)
{
	struct vsfusbh_ipheth_t *ipheth = (struct vsfusbh_ipheth_t *)netdrv->param;
	struct vsfhcd_urb_t *urb = ipheth->inurb;

	urb->transfer_buffer = netdrv->rxbuf.buffer.buffer;
	urb->transfer_length = netdrv->rxbuf.buffer.size;
	urb->free_buffer = netdrv->adapter->free_buf;
	urb->free_buffer_param = netdrv->rxbuf.netbuf;
	return vsfusbh_submit_urb(ipheth->usbh, urb);
}

static struct vsfip_link_op_t vsfusbh_ipheth_link_op =
{
	vsfusbh_ipheth_netdrv_init, vsfusbh_ipheth_netdrv_fini,
	vsfusbh_ipheth_netdrv_output, vsfusbh_ipheth_netdrv_input, vsfip_eth_routable
};

static void vsfusbh_ipheth_timer(void *param)
{
	struct vsfusbh_ipheth_t *ipheth = (struct vsfusbh_ipheth_t *)param;
	vsfsm_sem_post(&ipheth->carrier_work_sem);
}

static vsf_err_t vsfusbh_ipheth_init(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	struct vsfusbh_ipheth_t *ipheth = (struct vsfusbh_ipheth_t *)pt->user_data;
	struct vsfip_netdrv_t *netdrv = ipheth->netdrv;
	struct vsfhcd_urb_t *ctrlurb = ipheth->ctrlurb;
	struct vsfhcd_urb_t *inurb = ipheth->inurb;
	struct vsfusbh_t *usbh = ipheth->usbh;
	vsf_err_t err;

	vsfsm_pt_begin(pt);

	ctrlurb->notifier_sm = pt->sm;
	ctrlurb->transfer_buffer = ipheth->ctrlbuff;
	vsfsm_sem_init(&ipheth->carrier_work_sem, 0, VSFSM_EVT_USER);
	ipheth->timer = vsftimer_create_cb(5 * 1000, -1, vsfusbh_ipheth_timer, ipheth);

	while (ipheth->timer != NULL)
	{
		if (vsfsm_sem_pend(&ipheth->carrier_work_sem, pt->sm))
			vsfsm_pt_wfe(pt, VSFSM_EVT_USER);

		if (vsfsm_crit_enter(&ipheth->dev->ep0_crit, pt->sm))
			vsfsm_pt_wfe(pt, VSFSM_EVT_EP0_CRIT);

		ctrlurb->transfer_length = 1;
		ctrlurb->pipe = usb_rcvctrlpipe(ctrlurb->hcddev, 0);
		err = vsfusbh_control_msg(usbh, ctrlurb, USB_DIR_IN | USB_TYPE_VENDOR,
			IPHETH_REQ_CARRIER_CHECK, 0, ipheth->ifnum);
		if (err != VSFERR_NONE) goto fail;
		vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
		if ((ctrlurb->status != URB_OK) || (ctrlurb->actual_length != 1)) goto fail;

		vsfsm_crit_leave(&ipheth->dev->ep0_crit);

		if (ipheth->carrier == ipheth->ctrlbuff[0])
			continue;

		ipheth->carrier = (enum ipheth_carrier_t)ipheth->ctrlbuff[0];
		if (ipheth->carrier == IPHETH_CARRIER_ON)
		{
			vsfdbg_prints("ipheth: carrier on" VSFCFG_DEBUG_LINEEND);

			if (vsfsm_crit_enter(&ipheth->dev->ep0_crit, pt->sm))
			vsfsm_pt_wfe(pt, VSFSM_EVT_EP0_CRIT);

			ctrlurb->transfer_length = 4;
			ctrlurb->pipe = usb_rcvctrlpipe(ctrlurb->hcddev, 0);

			err = vsfusbh_control_msg(usbh, ctrlurb,
				USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
				IPHETH_REQ_UNKNOWN03, 0, ipheth->ifnum);
			if (err != VSFERR_NONE) goto fail;
			vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
			if ((ctrlurb->status != URB_OK) || (ctrlurb->actual_length != 4)) goto fail;
			vsfdbg_prints("ipheth: response to unknown request 0x03: ");
			vsfdbg_printb(ctrlurb->transfer_buffer, ctrlurb->actual_length, VSFDBG_DF_U8_16_N);

			err = vsfusbh_control_msg(usbh, ctrlurb,
				USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
				IPHETH_REQ_UNKNOWN02, 0, ipheth->ifnum);
			if (err != VSFERR_NONE) goto fail;
			vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
			if ((ctrlurb->status != URB_OK) || (ctrlurb->actual_length != 4)) goto fail;
			vsfdbg_prints("ipheth: response to unknown request 0x02: ");
			vsfdbg_printb(ctrlurb->transfer_buffer, ctrlurb->actual_length, VSFDBG_DF_U8_16_N);

			ctrlurb->transfer_length = 0;
			err = vsfusbh_set_interface(usbh, ctrlurb, ipheth->ifnum, ipheth->alt);
			if (err != VSFERR_NONE) goto fail;
			vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
			if (ctrlurb->status != URB_OK) goto fail;

			err = vsfusbh_control_msg(usbh, ctrlurb,
				USB_DIR_OUT | USB_RECIP_ENDPOINT, USB_REQ_CLEAR_FEATURE, 0,
				USB_DIR_IN | usb_pipeendpoint(ipheth->inurb->pipe));
			if (err != VSFERR_NONE) goto fail;
			vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
			if (ctrlurb->status != URB_OK) goto fail;

			err = vsfusbh_control_msg(usbh, ctrlurb,
				USB_DIR_OUT | USB_RECIP_ENDPOINT, USB_REQ_CLEAR_FEATURE, 0,
				USB_DIR_OUT | usb_pipeendpoint(ipheth->outurb->pipe));
			if (err != VSFERR_NONE) goto fail;
			vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
			if (ctrlurb->status != URB_OK) goto fail;

			if (!netdrv->macaddr.size)
			{
				ctrlurb->transfer_length = VSFIP_ETH_ADDRLEN;
				ctrlurb->pipe = usb_rcvctrlpipe(ctrlurb->hcddev, 0);
				err = vsfusbh_control_msg(usbh, ctrlurb, USB_DIR_IN | USB_TYPE_VENDOR,
						IPHETH_REQ_GETMAC, 0, ipheth->ifnum);
				if (err != VSFERR_NONE) goto fail;
				vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
				if ((ctrlurb->status != URB_OK) || (ctrlurb->actual_length != VSFIP_ETH_ADDRLEN))
					goto fail;

				netdrv->macaddr.size = VSFIP_ETH_ADDRLEN;
				memcpy(netdrv->macaddr.addr.s_addr_buf, ctrlurb->transfer_buffer,
						ctrlurb->actual_length);
			}

			vsfsm_crit_leave(&ipheth->dev->ep0_crit);

			if (!ipheth->insm.init_state.evt_handler)
			{
				ipheth->insm.init_state.evt_handler = vsfusbh_ipheth_urb_evthandler;
				ipheth->insm.user_data = ipheth;
				vsfsm_init(&ipheth->insm);
			}
			if (!ipheth->outsm.init_state.evt_handler)
			{
				ipheth->outsm.init_state.evt_handler = vsfusbh_ipheth_urb_evthandler;
				ipheth->outsm.user_data = ipheth;
				vsfsm_init(&ipheth->outsm);
			}

			inurb->transfer_buffer = ipheth->ctrlbuff;
			inurb->transfer_length = 4;
			vsfusbh_submit_urb(usbh, inurb);
		}
		else
		{
			vsfdbg_printf("ipheth: carrier off %d" VSFCFG_DEBUG_LINEEND,
							ipheth->carrier);
			if (vsfip_netdrv_connected(netdrv))
			{
				vsfdbg_prints("ipheth: Disonnected" VSFCFG_DEBUG_LINEEND);
				vsfip_netdrv_disconnect(netdrv);
			}

			if (vsfsm_crit_enter(&ipheth->dev->ep0_crit, pt->sm))
				vsfsm_pt_wfe(pt, VSFSM_EVT_EP0_CRIT);

			ctrlurb->transfer_length = 0;
			err = vsfusbh_set_interface(usbh, ctrlurb, ipheth->ifnum, 0);
			if (err != VSFERR_NONE) goto fail;
			vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
			if (ctrlurb->status != URB_OK) goto fail;

			vsfsm_crit_leave(&ipheth->dev->ep0_crit);

			// re-enable timer
			vsftimer_enqueue(ipheth->timer);
		}
	}

	vsfsm_pt_end(pt);
	return VSFERR_NONE;
fail:
	vsfsm_crit_leave(&ipheth->dev->ep0_crit);
	return VSFERR_FAIL;
}

static void *vsfusbh_ipheth_probe(struct vsfusbh_t *usbh,
			struct vsfusbh_device_t *dev, struct vsfusbh_ifs_t *ifs,
			const struct vsfusbh_device_id_t *id)
{
	struct usb_interface_desc_t *ifs_desc;
	struct usb_endpoint_desc_t *ep_desc;
	struct vsfusbh_ipheth_t *ipheth;
	struct vsfip_netdrv_t *netdrv;
	uint8_t ep;
	int alt;

	// only support the last configuration
	if (dev->cur_config < dev->device_desc->bNumConfigurations - 1) return NULL;
	// include requested alt and has 2 endpoints
	alt = 2;
	ifs_desc = ifs->alt[alt].ifs_desc;
	ep_desc = ifs->alt[alt].ep_desc;

	netdrv = vsfip_netdrv_new(sizeof(*ipheth), &vsfusbh_ipheth_link_op, &vsfusbh_ipheth_cb);
	if (netdrv == NULL) return NULL;
	ipheth = (struct vsfusbh_ipheth_t *)netdrv->param;
	memset(ipheth, 0, sizeof(struct vsfusbh_ipheth_t));
	ipheth->netdrv = netdrv;

	ipheth->ctrlurb = vsfusbh_alloc_urb(usbh);
	if (ipheth->ctrlurb == NULL) goto err_free_ipheth;
	ipheth->inurb = vsfusbh_alloc_urb(usbh);
	if (ipheth->inurb == NULL) goto err_free_ctrlurb;
	ipheth->outurb = vsfusbh_alloc_urb(usbh);
	if (ipheth->outurb == NULL) goto err_free_inurb;

	ipheth->ctrlurb->hcddev = &dev->hcddev;
	ipheth->inurb->hcddev = &dev->hcddev;
	ipheth->inurb->notifier_sm = &ipheth->insm;
	ipheth->outurb->hcddev = &dev->hcddev;
	ipheth->outurb->notifier_sm = &ipheth->outsm;
	ipheth->outurb->transfer_flags = URB_ZERO_PACKET;

	for (int i = 0; i < 2; i++)
	{
		ep = ep_desc->bEndpointAddress;
		if (ep & 0x80)
			ipheth->inurb->pipe = usb_rcvbulkpipe(&dev->hcddev, ep & 0x7F);
		else
			ipheth->outurb->pipe = usb_sndbulkpipe(&dev->hcddev, ep & 0x7F);
		ep_desc = (struct usb_endpoint_desc_t *)((uint32_t)ep_desc + ep_desc->bLength);
	}

	ipheth->netdrv->op = &vsfusbh_ipheth_link_op;
	ipheth->netdrv->param = ipheth;

	ipheth->dev = dev;
	ipheth->usbh = usbh;
	ipheth->dev = dev;
	ipheth->ifnum = ifs_desc->bInterfaceNumber;
	ipheth->alt = ifs_desc->bAlternateSetting;
	ipheth->initpt.user_data = ipheth;
	ipheth->initpt.thread = vsfusbh_ipheth_init;
	vsfsm_pt_init(&ipheth->initsm, &ipheth->initpt);
	return ipheth;

err_free_inurb:
	vsfusbh_free_urb(usbh, &ipheth->inurb);
err_free_ctrlurb:
	vsfusbh_free_urb(usbh, &ipheth->ctrlurb);
err_free_ipheth:
	vsf_bufmgr_free(ipheth);
	return NULL;
}

static void vsfusbh_ipheth_disconnect(struct vsfusbh_t *usbh,
		struct vsfusbh_device_t *dev, void *priv)
{
	struct vsfusbh_ipheth_t *ipheth = priv;
	struct vsfip_netdrv_t *netdrv = ipheth->netdrv;

	if (ipheth->timer != NULL)
	{
		vsftimer_free(ipheth->timer);
		ipheth->timer = NULL;
	}
	vsfusbh_free_urb(usbh, &ipheth->ctrlurb);
	vsfusbh_free_urb(usbh, &ipheth->inurb);
	vsfusbh_free_urb(usbh, &ipheth->outurb);

	vsfsm_fini(&ipheth->initsm);
	vsfsm_fini(&ipheth->insm);
	vsfsm_fini(&ipheth->outsm);

	netdrv->tofree = true;
	if (vsfip_netdrv_connected(netdrv))
		vsfip_netdrv_disconnect(netdrv);
	else
		vsfip_netdrv_free(netdrv);
}

static const struct vsfusbh_device_id_t vsfusbh_ipheth_id_table[] =
{
	{
		.match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_CLASS |
			USB_DEVICE_ID_MATCH_INT_SUBCLASS | USB_DEVICE_ID_MATCH_INT_PROTOCOL,
		.idVendor = 0x05AC,
		.bInterfaceClass = 0xFF,
		.bInterfaceSubClass = 0xFD,
		.bInterfaceProtocol = 0x01,
	},
	{0},
};

const struct vsfusbh_class_drv_t vsfusbh_ipheth_drv =
{
	.name = "ipheth",
	.id_table = vsfusbh_ipheth_id_table,
	.probe = vsfusbh_ipheth_probe,
	.disconnect = vsfusbh_ipheth_disconnect,
};
